<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
<meta name="description" content="TypeScript: Display hierarchical data by nesting, where the area of each node is proportional to some value for the node.  Clicking consecutively results in selecting containing Groups."/> 
<link rel="stylesheet" href="../assets/css/style.css"/> 
<!-- Copyright 1998-2021 by Northwoods Software Corporation. -->
<title>Tree Map</title>
</head>

<body>
  <!-- This top nav is not part of the sample code -->
  <nav id="navTop" class="w-full z-30 top-0 text-white bg-nwoods-primary">
    <div class="w-full container max-w-screen-lg mx-auto flex flex-wrap sm:flex-nowrap items-center justify-between mt-0 py-2">
      <div class="md:pl-4">
        <a class="text-white hover:text-white no-underline hover:no-underline
        font-bold text-2xl lg:text-4xl rounded-lg hover:bg-nwoods-secondary " href="../">
          <h1 class="mb-0 p-1 ">GoJS</h1>
        </a>
      </div>
      <button id="topnavButton" class="rounded-lg sm:hidden focus:outline-none focus:ring" aria-label="Navigation">
        <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6">
          <path id="topnavOpen" fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z" clip-rule="evenodd"></path>
          <path id="topnavClosed" class="hidden" fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
        </svg>
      </button>
      <div id="topnavList" class="hidden lg:text-base sm:block items-center w-auto mt-0 text-white p-0 z-20">
        <ul class="list-reset list-none font-semibold flex justify-end flex-wrap sm:flex-nowrap items-center px-0 pb-0">
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../learn/">Learn</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../samples/">Samples</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../intro/">Intro</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../api/">API</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/products/register.html">Register</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../download.html">Download</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="https://forum.nwoods.com/c/gojs/11">Forum</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/contact.html"
           target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/sales/index.html"
           target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a></li>
        </ul>
      </div>
    </div>
    <hr class="border-b border-gray-600 opacity-50 my-0 py-0" />
  </nav>
  <div class="md:flex flex-col md:flex-row md:min-h-screen w-full max-w-screen-xl mx-auto">
    <div id="navSide" class="flex flex-col w-full md:w-48 text-gray-700 bg-white flex-shrink-0"></div>
    <!-- * * * * * * * * * * * * * -->
    <!-- Start of GoJS sample code -->
    

    <div class="p-4 w-full">
  <div id="sample">
    <div style="margin-bottom: 5px; padding: 5px; background-color: aliceblue">
      <span style="display: inline-block; vertical-align: top; padding: 5px">
        <b>New Tree</b><br />
        MinNodes: <input type="number" width="2" id="minNodes" value="300" /><br />
        MaxNodes: <input type="number" width="2" id="maxNodes" value="500" /><br />
        MinChildren: <input type="number" width="2" id="minChil" value="2" /><br />
        MaxChildren: <input type="number" width="2" id="maxChil" value="5" /><br />
        <button type="button" id="rebuildGraph">Generate Tree</button>
      </span>
    </div>
    <div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 500px"></div>
    <p>
      This sample demonstrates a custom Layout, TreeMapLayout, which assumes that the diagram consists of nested Groups and simple Nodes.
      Each node is positioned and sized to fill an area of the viewport proportionate to its "size", as determined by its Node.data.size property.
      Each Group gets a size that is the sum of all of its member Nodes.
    </p>
    <p>
      The layout is defined in its own file, as <a href="TreeMapLayout.ts">TreeMapLayout.ts</a>.
    </p>
    <p>
      Clicking repeatedly at the same point will initially select the Node at that point, and then its containing Group, and so on up the chain of containers.
    </p>
  </div>

  <script type="module" id="code">
    import * as go from "../release/go-module.js";
    import { TreeMapLayout } from './TreeMapLayout.js';

    if (window.goSamples) goSamples(); // init for the samples -- you don't need to call this
    const $ = go.GraphObject.make; // for conciseness in defining templates

    const myDiagram =
      $(go.Diagram, 'myDiagramDiv', // must be the ID or reference to div
        {
          initialAutoScale: go.Diagram.Uniform,
          'animationManager.isEnabled': false,
          layout: $(TreeMapLayout, { isTopLevelHorizontal: false }),
          allowMove: false, allowCopy: false, allowDelete: false
        });

    // change selection behavior to cycle up the chain of containing Groups
    myDiagram.toolManager.clickSelectingTool.standardMouseSelect = function() {
      const diagram = this.diagram;
      if (diagram === null || !diagram.allowSelect) return;
      const e = diagram.lastInput;
      if (!(e.control || e.meta) && !e.shift) {
        const part = diagram.findPartAt(e.documentPoint, false);
        if (part !== null) {
          let firstselected = null; // is this or any containing Group selected?
          let node = part;
          while (node !== null) {
            if (node.isSelected) {
              firstselected = node;
              break;
            } else {
              node = node.containingGroup;
            }
          }
          if (firstselected !== null) { // deselect this and select its containing Group
            firstselected.isSelected = false;
            const group = firstselected.containingGroup;
            if (group !== null)
              group.isSelected = true;
            return;
          }
        }
      }
      go.ClickSelectingTool.prototype.standardMouseSelect.call(this);
    };

    // Nodes and Groups are the absolute minimum template: no elements at all!
    myDiagram.nodeTemplate =
      $(go.Node,
        { background: 'rgba(99,99,99,0.2)' },
        new go.Binding('background', 'fill'),
        {
          toolTip: $('ToolTip',
            $(go.TextBlock, new go.Binding('text', '', tooltipString).ofObject()))
        });

    myDiagram.groupTemplate =
      $(go.Group, 'Auto',
        { layout: null },
        { background: 'rgba(99,99,99,0.2)' },
        new go.Binding('background', 'fill'),
        {
          toolTip: $('ToolTip',
            $(go.TextBlock, new go.Binding('text', '', tooltipString).ofObject()))
        });

    function tooltipString(part) {
      if (part instanceof go.Adornment && part.adornedPart !== null) part = part.adornedPart;
      let msg = createPath(part);
      msg += '\nsize: ' + part.data.size;
      if (part instanceof go.Group) {
        const group = part;
        msg += '\n# children: ' + group.memberParts.count;
        msg += '\nsubtotal size: ' + group.data.total;
      }
      return msg;
    }

    function createPath(part) {
      const parent = part.containingGroup;
      return (parent !== null ? createPath(parent) + '/' : '') + part.data.text;
    }

    // generate a tree with the default values
    rebuildGraph();

    function rebuildGraph() {
      let minNodes = document.getElementById('minNodes').value;
      minNodes = parseInt(minNodes, 10);
      let maxNodes = document.getElementById('maxNodes').value;
      maxNodes = parseInt(maxNodes, 10);
      let minChil = document.getElementById('minChil').value;
      minChil = parseInt(minChil, 10);
      let maxChil = document.getElementById('maxChil').value;
      maxChil = parseInt(maxChil, 10);
      // create and assign a new model
      const model = new go.GraphLinksModel();
      model.nodeGroupKeyProperty = 'parent';
      model.nodeDataArray = generateNodeData(minNodes, maxNodes, minChil, maxChil);
      myDiagram.model = model;
    }

    // Creates a random number (between MIN and MAX) of randomly colored nodes.
    function generateNodeData(minNodes, maxNodes, minChil, maxChil) {
      const nodeArray = [];
      if (minNodes === undefined || isNaN(minNodes) || minNodes < 1)
        minNodes = 1;
      if (maxNodes === undefined || isNaN(maxNodes) || maxNodes < minNodes)
        maxNodes = minNodes;
      // Create a bunch of node data
      const numNodes = Math.floor(Math.random() * (maxNodes - minNodes + 1)) + minNodes;
      for (let i = 0; i < numNodes; i++) {
        const size = Math.random() * Math.random() * 10000; // non-uniform distribution
        // nodeArray.push(new nodes(i, false, undefined, i.toString(), go.Brush.randomColor(), size, -1));
        nodeArray.push({
          key: i,
          isGroup: false,
          parent: undefined,
          text: i.toString(),
          fill: go.Brush.randomColor(),
          size: size,
          total: -1 // use a negative value to indicate that the total for the group has not been computed
        });
      }
      // Takes the random collection of node data and creates a random tree with them.
      // Respects the minimum and maximum number of links from each node.
      // The minimum can be disregarded if we run out of nodes to link to.
      if (nodeArray.length > 1) {
        if (minChil === undefined || isNaN(minChil) || minChil < 0)
          minChil = 0;
        if (maxChil === undefined || isNaN(maxChil) || maxChil < minChil)
          maxChil = minChil;
        // keep the Set of node data that do not yet have a parent
        const available = new go.Set();
        available.addAll(nodeArray);
        for (let i = 0; i < nodeArray.length; i++) {
          const parent = nodeArray[i];
          available.remove(parent);
          // assign some number of node data as children of this parent node data
          const children = Math.floor(Math.random() * (maxChil - minChil + 1)) + minChil;
          for (let j = 0; j < children; j++) {
            const child = available.first();
            if (child === null)
              break; // oops, ran out already
            available.remove(child);
            // have the child node data refer to the parent node data by its key
            child.parent = parent.key;
            if (!parent.isGroup) { // make sure PARENT is a group
              parent.isGroup = true;
            }
            let par = parent;
            while (par !== null) {
              par.total += child.total; // sum up sizes of all children
              if (par.parent !== undefined) {
                par = nodeArray[par.parent];
              }
              else {
                break;
              }
            }
          }
        }
      }
      return nodeArray;
    }
    document.getElementById("rebuildGraph").onclick = rebuildGraph;

    window.myDiagram = myDiagram; // Attach to the window for console debugging
  </script>
    </div>
    <!-- * * * * * * * * * * * * * -->
    <!--  End of GoJS sample code  -->
  </div>
</body>
<!--  This script is part of the gojs.net website, and is not needed to run the sample -->
<script src="../assets/js/goSamples.js"></script>
</html>
